#include "intercept.h"
#include "intercept_util.h"
#include "intercept_log.h"
#include <assert.h>
#include <stdint.h>
#include <syscall.h>
#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
enum { TRAMPOLINE_SIZE = 6 + 8 };
static void create_wrapper(struct patch_desc *patch, unsigned char **dst);
static unsigned char *
create_absolute_jump(unsigned char *from, void *to)
{
*from++ = 0xff;
*from++ = 0x25;
*from++ = 0;
*from++ = 0;
*from++ = 0;
*from++ = 0;
unsigned char *d = (unsigned char *)&to;
*from++ = d[0];
*from++ = d[1];
*from++ = d[2];
*from++ = d[3];
*from++ = d[4];
*from++ = d[5];
*from++ = d[6];
*from++ = d[7];
return from;
}
void
create_jump(unsigned char opcode, unsigned char *from, void *to)
{
ptrdiff_t delta = ((unsigned char *)to) - (from + JUMP_INS_SIZE);
if (delta > ((ptrdiff_t)INT32_MAX) || delta < ((ptrdiff_t)INT32_MIN))
xabort("create_jump distance check");
int32_t delta32 = (int32_t)delta;
unsigned char *d = (unsigned char *)&delta32;
from[0] = opcode;
from[1] = d[0];
from[2] = d[1];
from[3] = d[2];
from[4] = d[3];
}
static void
check_trampoline_usage(const struct intercept_desc *desc)
{
if (!desc->uses_trampoline_table)
return;
size_t used = (size_t)(desc->next_trampoline - desc->trampoline_table);
if (used + TRAMPOLINE_SIZE >= desc->trampoline_table_size)
xabort("trampoline space not enough");
}
static bool
is_nop_in_range(unsigned char *address, const struct range *nop)
{
unsigned char *dst = nop->address + 2;
unsigned char *src = address + 2;
unsigned char *reach_min = src - 128;
unsigned char *reach_max = src + 127;
return reach_min <= dst && dst <= reach_max;
}
static void
assign_nop_trampoline(struct intercept_desc *desc,
struct patch_desc *patch,
size_t *next_nop_i)
{
struct range *nop = desc->nop_table + *next_nop_i;
if (*next_nop_i >= desc->nop_count) {
patch->uses_nop_trampoline = false;
return;
}
if (is_nop_in_range(patch->syscall_addr, nop)) {
patch->uses_nop_trampoline = true;
patch->nop_trampoline = *nop;
++(*next_nop_i);
return;
}
if (nop->address > patch->syscall_addr) {
patch->uses_nop_trampoline = false;
return;
}
++(*next_nop_i);
assign_nop_trampoline(desc, patch, next_nop_i);
}
static bool
is_copiable_before_syscall(struct intercept_disasm_result ins)
{
if (!ins.is_set)
return false;
return !(ins.has_ip_relative_opr ||
ins.is_call ||
ins.is_rel_jump ||
ins.is_jump ||
ins.is_ret ||
ins.is_endbr ||
ins.is_syscall);
}
static bool
is_copiable_after_syscall(struct intercept_disasm_result ins)
{
if (!ins.is_set)
return false;
return !(ins.has_ip_relative_opr ||
ins.is_call ||
ins.is_rel_jump ||
ins.is_jump ||
ins.is_endbr ||
ins.is_syscall);
}
static void
check_surrounding_instructions(struct intercept_desc *desc,
struct patch_desc *patch)
{
patch->uses_prev_ins = patch->preceding_ins.is_lea_rip ||
(is_copiable_before_syscall(patch->preceding_ins) &&
!is_overwritable_nop(&patch->preceding_ins) &&
!has_jump(desc, patch->syscall_addr));
if (patch->uses_prev_ins) {
patch->uses_prev_ins_2 = patch->preceding_ins_2.is_lea_rip ||
(patch->uses_prev_ins &&
is_copiable_before_syscall(patch->preceding_ins_2) &&
!is_overwritable_nop(&patch->preceding_ins_2) &&
!has_jump(desc, patch->syscall_addr
- patch->preceding_ins.length));
} else {
patch->uses_prev_ins_2 = false;
}
patch->uses_next_ins = patch->following_ins.is_lea_rip ||
(is_copiable_after_syscall(patch->following_ins) &&
!is_overwritable_nop(&patch->following_ins) &&
!has_jump(desc, patch->syscall_addr + SYSCALL_INS_SIZE));
}
void
create_patch_wrappers(struct intercept_desc *desc, unsigned char **dst)
{
size_t next_nop_i = 0;
for (unsigned patch_i = 0; patch_i < desc->count; ++patch_i) {
struct patch_desc *patch = desc->items + patch_i;
debug_dump("patching %s:0x%lx\n", desc->path,
patch->syscall_addr - desc->base_addr);
assign_nop_trampoline(desc, patch, &next_nop_i);
if (patch->uses_nop_trampoline) {
patch->uses_prev_ins = false;
patch->uses_prev_ins_2 = false;
patch->uses_next_ins = false;
patch->dst_jmp_patch =
patch->nop_trampoline.address + 2;
patch->return_address =
patch->syscall_addr + SYSCALL_INS_SIZE;
} else {
check_surrounding_instructions(desc, patch);
unsigned length = SYSCALL_INS_SIZE;
patch->dst_jmp_patch = patch->syscall_addr;
if (patch->uses_prev_ins) {
length += patch->preceding_ins.length;
patch->dst_jmp_patch -=
patch->preceding_ins.length;
if (patch->uses_prev_ins_2) {
length += patch->preceding_ins_2.length;
patch->dst_jmp_patch -=
patch->preceding_ins_2.length;
}
}
if (patch->uses_next_ins) {
length += patch->following_ins.length;
patch->return_address = patch->syscall_addr +
SYSCALL_INS_SIZE +
patch->following_ins.length;
} else {
patch->return_address =
patch->syscall_addr + SYSCALL_INS_SIZE;
}
if (length < JUMP_INS_SIZE) {
char buffer[0x1000];
int l = snprintf(buffer, sizeof(buffer),
"unintercepted syscall at: %s 0x%lx\n",
desc->path,
patch->syscall_offset);
intercept_log(buffer, (size_t)l);
xabort("not enough space for patching"
" around syscal");
}
}
mark_jump(desc, patch->return_address);
create_wrapper(patch, dst);
}
}
extern unsigned char intercept_asm_wrapper_tmpl[];
extern unsigned char intercept_asm_wrapper_tmpl_end;
extern unsigned char intercept_asm_wrapper_patch_desc_addr;
extern unsigned char intercept_asm_wrapper_wrapper_level1_addr;
extern unsigned char intercept_wrapper;
size_t asm_wrapper_tmpl_size;
static ptrdiff_t o_patch_desc_addr;
static ptrdiff_t o_wrapper_level1_addr;
bool intercept_routine_must_save_ymm;
void
init_patcher(void)
{
unsigned char *begin = &intercept_asm_wrapper_tmpl[0];
assert(&intercept_asm_wrapper_tmpl_end > begin);
assert(&intercept_asm_wrapper_patch_desc_addr > begin);
assert(&intercept_asm_wrapper_wrapper_level1_addr > begin);
assert(&intercept_asm_wrapper_patch_desc_addr <
&intercept_asm_wrapper_tmpl_end);
assert(&intercept_asm_wrapper_wrapper_level1_addr <
&intercept_asm_wrapper_tmpl_end);
asm_wrapper_tmpl_size =
(size_t)(&intercept_asm_wrapper_tmpl_end - begin);
o_patch_desc_addr = &intercept_asm_wrapper_patch_desc_addr - begin;
o_wrapper_level1_addr =
&intercept_asm_wrapper_wrapper_level1_addr - begin;
extern bool has_ymm_registers(void);
intercept_routine_must_save_ymm = has_ymm_registers();
}
static unsigned char *
create_movabs(unsigned char *code, uint64_t value, unsigned char reg_bits)
{
assert(reg_bits < 16);
unsigned char *bytes = (unsigned char *)&value;
*code++ = 0x48 | (reg_bits >> 3);
*code++ = 0xb8 | (reg_bits & 7);
*code++ = bytes[0];
*code++ = bytes[1];
*code++ = bytes[2];
*code++ = bytes[3];
*code++ = bytes[4];
*code++ = bytes[5];
*code++ = bytes[6];
*code++ = bytes[7];
return code;
}
static unsigned char *
create_movabs_r11(unsigned char *code, uint64_t value)
{
return create_movabs(code, value, 11);
}
static unsigned char *
relocate_instruction(unsigned char *dst,
const struct intercept_disasm_result *ins)
{
if (ins->is_lea_rip) {
return create_movabs(dst, (uint64_t)ins->rip_ref_addr,
ins->arg_register_bits);
} else {
memcpy(dst, ins->address, ins->length);
return dst + ins->length;
}
}
static void
create_wrapper(struct patch_desc *patch, unsigned char **dst)
{
patch->asm_wrapper = *dst;
if (patch->uses_prev_ins) {
if (patch->uses_prev_ins_2)
*dst = relocate_instruction(*dst,
&patch->preceding_ins_2);
*dst = relocate_instruction(*dst, &patch->preceding_ins);
}
memcpy(*dst, intercept_asm_wrapper_tmpl, asm_wrapper_tmpl_size);
create_movabs_r11(*dst + o_patch_desc_addr, (uintptr_t)patch);
create_movabs_r11(*dst + o_wrapper_level1_addr,
(uintptr_t)&intercept_wrapper);
*dst += asm_wrapper_tmpl_size;
if (patch->uses_next_ins)
*dst = relocate_instruction(*dst, &patch->following_ins);
*dst = create_absolute_jump(*dst, patch->return_address);
}
static void
create_short_jump(unsigned char *from, unsigned char *to)
{
ptrdiff_t d = to - (from + 2);
if (d < - 128 || d > 127)
xabort("create_short_jump distance check");
from[0] = SHORT_JMP_OPCODE;
from[1] = (unsigned char)((char)d);
}
static unsigned char *
after_nop(const struct range *nop)
{
return nop->address + nop->size;
}
void
activate_patches(struct intercept_desc *desc)
{
unsigned char *first_page;
size_t size;
if (desc->count == 0)
return;
first_page = round_down_address(desc->text_start);
size = (size_t)(desc->text_end - first_page);
mprotect_no_intercept(first_page, size,
PROT_READ | PROT_WRITE | PROT_EXEC,
"mprotect PROT_READ | PROT_WRITE | PROT_EXEC");
for (unsigned i = 0; i < desc->count; ++i) {
const struct patch_desc *patch = desc->items + i;
if (patch->dst_jmp_patch < desc->text_start ||
patch->dst_jmp_patch > desc->text_end)
xabort("dst_jmp_patch outside text");
if (desc->uses_trampoline_table) {
check_trampoline_usage(desc);
create_jump(JMP_OPCODE,
patch->dst_jmp_patch, desc->next_trampoline);
desc->next_trampoline = create_absolute_jump(
desc->next_trampoline, patch->asm_wrapper);
} else {
create_jump(JMP_OPCODE,
patch->dst_jmp_patch, patch->asm_wrapper);
}
if (patch->uses_nop_trampoline) {
create_short_jump(patch->syscall_addr,
patch->dst_jmp_patch);
create_short_jump(patch->nop_trampoline.address,
after_nop(&patch->nop_trampoline));
} else {
unsigned char *byte;
for (byte = patch->dst_jmp_patch + JUMP_INS_SIZE;
byte < patch->return_address;
++byte) {
*byte = INT3_OPCODE;
}
}
}
mprotect_no_intercept(first_page, size,
PROT_READ | PROT_EXEC,
"mprotect PROT_READ | PROT_EXEC");
}