#include <assert.h>
#include <syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include "intercept.h"
#include "intercept_util.h"
#include "disasm_wrapper.h"
static int
open_orig_file(const struct intercept_desc *desc)
{
int fd;
fd = syscall_no_intercept(SYS_open, desc->path, O_RDONLY);
xabort_on_syserror(fd, __func__);
return fd;
}
static void
add_table_info(struct section_list *list, const Elf64_Shdr *header)
{
size_t max = sizeof(list->headers) / sizeof(list->headers[0]);
if (list->count < max) {
list->headers[list->count] = *header;
list->count++;
} else {
xabort("allocated section_list exhausted");
}
}
static void
add_text_info(struct intercept_desc *desc, const Elf64_Shdr *header,
Elf64_Half index)
{
desc->text_offset = header->sh_offset;
desc->text_start = desc->base_addr + header->sh_addr;
desc->text_end = desc->text_start + header->sh_size - 1;
desc->text_section_index = index;
}
static void
find_sections(struct intercept_desc *desc, int fd)
{
Elf64_Ehdr elf_header;
desc->symbol_tables.count = 0;
desc->rela_tables.count = 0;
xread(fd, &elf_header, sizeof(elf_header));
Elf64_Shdr sec_headers[elf_header.e_shnum];
xlseek(fd, elf_header.e_shoff, SEEK_SET);
xread(fd, sec_headers, elf_header.e_shnum * sizeof(Elf64_Shdr));
char sec_string_table[sec_headers[elf_header.e_shstrndx].sh_size];
xlseek(fd, sec_headers[elf_header.e_shstrndx].sh_offset, SEEK_SET);
xread(fd, sec_string_table,
sec_headers[elf_header.e_shstrndx].sh_size);
bool text_section_found = false;
for (Elf64_Half i = 0; i < elf_header.e_shnum; ++i) {
const Elf64_Shdr *section = &sec_headers[i];
char *name = sec_string_table + section->sh_name;
debug_dump("looking at section: \"%s\" type: %ld\n",
name, (long)section->sh_type);
if (strcmp(name, ".text") == 0) {
text_section_found = true;
add_text_info(desc, section, i);
} else if (section->sh_type == SHT_SYMTAB ||
section->sh_type == SHT_DYNSYM) {
debug_dump("found symbol table: %s\n", name);
add_table_info(&desc->symbol_tables, section);
} else if (section->sh_type == SHT_RELA) {
debug_dump("found relocation table: %s\n", name);
add_table_info(&desc->rela_tables, section);
}
}
if (!text_section_found)
xabort("text section not found");
}
static void
allocate_jump_table(struct intercept_desc *desc)
{
assert(desc->text_start < desc->text_end);
size_t bytes = (size_t)(desc->text_end - desc->text_start + 1);
desc->jump_table = xmmap_anon(bytes / 8 + 1);
}
static size_t
calculate_table_count(const struct intercept_desc *desc)
{
assert(desc->text_start < desc->text_end);
size_t bytes = (size_t)(desc->text_end - desc->text_start + 1);
if (bytes > 0x10000)
return bytes / 64;
else
return 1024;
}
static void
allocate_nop_table(struct intercept_desc *desc)
{
desc->max_nop_count = calculate_table_count(desc);
desc->nop_count = 0;
desc->nop_table =
xmmap_anon(desc->max_nop_count * sizeof(desc->nop_table[0]));
}
static void
mark_nop(struct intercept_desc *desc, unsigned char *address, size_t size)
{
if (desc->nop_count == desc->max_nop_count)
return;
desc->nop_table[desc->nop_count].address = address;
desc->nop_table[desc->nop_count].size = size;
desc->nop_count++;
}
static bool
is_bit_set(const unsigned char *table, uint64_t offset)
{
return table[offset / 8] & (1 << (offset % 8));
}
static void
set_bit(unsigned char *table, uint64_t offset)
{
unsigned char tmp = (unsigned char)(1 << (offset % 8));
table[offset / 8] |= tmp;
}
bool
has_jump(const struct intercept_desc *desc, unsigned char *addr)
{
if (addr >= desc->text_start && addr <= desc->text_end)
return is_bit_set(desc->jump_table,
(uint64_t)(addr - desc->text_start));
else
return false;
}
void
mark_jump(const struct intercept_desc *desc, const unsigned char *addr)
{
if (addr >= desc->text_start && addr <= desc->text_end)
set_bit(desc->jump_table, (uint64_t)(addr - desc->text_start));
}
static void
find_jumps_in_section_syms(struct intercept_desc *desc, Elf64_Shdr *section,
int fd)
{
assert(section->sh_type == SHT_SYMTAB ||
section->sh_type == SHT_DYNSYM);
size_t sym_count = section->sh_size / sizeof(Elf64_Sym);
Elf64_Sym syms[sym_count];
xlseek(fd, section->sh_offset, SEEK_SET);
xread(fd, &syms, section->sh_size);
for (size_t i = 0; i < sym_count; ++i) {
if (ELF64_ST_TYPE(syms[i].st_info) != STT_FUNC)
continue;
if (syms[i].st_shndx != desc->text_section_index)
continue;
debug_dump("jump target: %lx\n",
(unsigned long)syms[i].st_value);
unsigned char *address = desc->base_addr + syms[i].st_value;
mark_jump(desc, address);
if (syms[i].st_size != 0)
mark_jump(desc, address + syms[i].st_size);
}
}
static void
find_jumps_in_section_rela(struct intercept_desc *desc, Elf64_Shdr *section,
int fd)
{
assert(section->sh_type == SHT_RELA);
size_t sym_count = section->sh_size / sizeof(Elf64_Rela);
Elf64_Rela syms[sym_count];
xlseek(fd, section->sh_offset, SEEK_SET);
xread(fd, &syms, section->sh_size);
for (size_t i = 0; i < sym_count; ++i) {
switch (ELF64_R_TYPE(syms[i].r_info)) {
case R_X86_64_RELATIVE:
case R_X86_64_RELATIVE64:
debug_dump("jump target: %lx\n",
(unsigned long)syms[i].r_addend);
unsigned char *address =
desc->base_addr + syms[i].r_addend;
mark_jump(desc, address);
break;
}
}
}
static bool
has_pow2_count(const struct intercept_desc *desc)
{
return (desc->count & (desc->count - 1)) == 0;
}
static struct patch_desc *
add_new_patch(struct intercept_desc *desc)
{
if (desc->count == 0) {
desc->items = xmmap_anon(sizeof(desc->items[0]));
} else if (has_pow2_count(desc)) {
size_t size = desc->count * sizeof(desc->items[0]);
desc->items = xmremap(desc->items, size, 2 * size);
}
return &(desc->items[desc->count++]);
}
bool
is_overwritable_nop(const struct intercept_disasm_result *ins)
{
return ins->is_nop && ins->length >= 2 + 5;
}
static void
crawl_text(struct intercept_desc *desc)
{
unsigned char *code = desc->text_start;
struct intercept_disasm_result prevs[3] = {{0, }};
unsigned has_prevs = 0;
struct intercept_disasm_context *context =
intercept_disasm_init(desc->text_start, desc->text_end);
while (code <= desc->text_end) {
struct intercept_disasm_result result;
result = intercept_disasm_next_instruction(context, code);
if (result.length == 0) {
++code;
continue;
}
if (result.has_ip_relative_opr)
mark_jump(desc, result.rip_ref_addr);
if (is_overwritable_nop(&result))
mark_nop(desc, code, result.length);
if (has_prevs >= 1 && prevs[2].is_syscall) {
struct patch_desc *patch = add_new_patch(desc);
patch->containing_lib_path = desc->path;
patch->preceding_ins_2 = prevs[0];
patch->preceding_ins = prevs[1];
patch->following_ins = result;
patch->syscall_addr = code - SYSCALL_INS_SIZE;
ptrdiff_t syscall_offset = patch->syscall_addr -
(desc->text_start - desc->text_offset);
assert(syscall_offset >= 0);
patch->syscall_offset = (unsigned long)syscall_offset;
}
prevs[0] = prevs[1];
prevs[1] = prevs[2];
prevs[2] = result;
if (has_prevs < 2)
++has_prevs;
code += result.length;
}
intercept_disasm_destroy(context);
}
static uintptr_t
get_min_address(void)
{
static uintptr_t min_address;
if (min_address != 0)
return min_address;
min_address = 0x10000;
int fd = syscall_no_intercept(SYS_open, "/proc/sys/vm/mmap_min_addr",
O_RDONLY);
if (fd >= 0) {
char line[64];
ssize_t r;
r = syscall_no_intercept(SYS_read, fd, line, sizeof(line) - 1);
if (r > 0) {
line[r] = '\0';
min_address = (uintptr_t)atoll(line);
}
syscall_no_intercept(SYS_close, fd);
}
return min_address;
}
void
allocate_trampoline_table(struct intercept_desc *desc)
{
char *e = getenv("INTERCEPT_NO_TRAMPOLINE");
desc->uses_trampoline_table = (e == NULL) || (e[0] == '0');
if (!desc->uses_trampoline_table) {
desc->trampoline_table = NULL;
desc->trampoline_table_size = 0;
desc->trampoline_table = NULL;
return;
}
FILE *maps;
char line[0x2000];
unsigned char *guess;
size_t size;
if ((uintptr_t)desc->text_end < INT32_MAX) {
guess = (void *)0;
} else {
guess = desc->text_end - INT32_MAX;
guess = (unsigned char *)(((uintptr_t)guess)
& ~((uintptr_t)(0xfff))) + 0x1000;
}
if ((uintptr_t)guess < get_min_address())
guess = (void *)get_min_address();
size = 64 * 0x1000;
if ((maps = fopen("/proc/self/maps", "r")) == NULL)
xabort("fopen /proc/self/maps");
while ((fgets(line, sizeof(line), maps)) != NULL) {
unsigned char *start;
unsigned char *end;
if (sscanf(line, "%p-%p", (void **)&start, (void **)&end) != 2)
xabort("sscanf from /proc/self/maps");
if (end < guess)
continue;
if (start >= guess + size) {
break;
}
guess = end;
if (guess + size >= desc->text_start + INT32_MAX) {
xabort("unable to find place for trampoline table");
}
}
fclose(maps);
desc->trampoline_table = mmap(guess, size,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE | MAP_ANON,
-1, 0);
if (desc->trampoline_table == MAP_FAILED)
xabort("unable to allocate space for trampoline table");
desc->trampoline_table_size = size;
desc->next_trampoline = desc->trampoline_table;
}
void
find_syscalls(struct intercept_desc *desc)
{
debug_dump("find_syscalls in %s "
"at base_addr 0x%016" PRIxPTR "\n",
desc->path,
(uintptr_t)desc->base_addr);
desc->count = 0;
int fd = open_orig_file(desc);
find_sections(desc, fd);
debug_dump(
"%s .text mapped at 0x%016" PRIxPTR " - 0x%016" PRIxPTR " \n",
desc->path,
(uintptr_t)desc->text_start,
(uintptr_t)desc->text_end);
allocate_jump_table(desc);
allocate_nop_table(desc);
for (Elf64_Half i = 0; i < desc->symbol_tables.count; ++i)
find_jumps_in_section_syms(desc,
desc->symbol_tables.headers + i, fd);
for (Elf64_Half i = 0; i < desc->rela_tables.count; ++i)
find_jumps_in_section_rela(desc,
desc->rela_tables.headers + i, fd);
syscall_no_intercept(SYS_close, fd);
crawl_text(desc);
}