-rw-r--r-- 18477 saferewrite-20250228/elfulator.c raw
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <inttypes.h>
static int print_interrupts = 0;
static int print_elf = 0;
static uint16_t uint16_t_load(const unsigned char *s) {
uint16_t z = 0;
z |= ((uint16_t) (*s++)) << 0;
z |= ((uint16_t) (*s++)) << 8;
return z;
}
static uint16_t uint16_t_load_bigendian(const unsigned char *s) {
uint16_t z = 0;
z |= ((uint16_t) (*s++)) << 8;
z |= ((uint16_t) (*s++)) << 0;
return z;
}
static uint32_t uint32_t_load(const unsigned char *s) {
uint32_t z = 0;
z |= ((uint32_t) (*s++)) << 0;
z |= ((uint32_t) (*s++)) << 8;
z |= ((uint32_t) (*s++)) << 16;
z |= ((uint32_t) (*s++)) << 24;
return z;
}
static uint32_t uint32_t_load_bigendian(const unsigned char *s) {
uint32_t z = 0;
z |= ((uint32_t) (*s++)) << 24;
z |= ((uint32_t) (*s++)) << 16;
z |= ((uint32_t) (*s++)) << 8;
z |= ((uint32_t) (*s++)) << 0;
return z;
}
static uint64_t uint64_t_load(const unsigned char *s) {
uint64_t z = 0;
z |= ((uint64_t) (*s++)) << 0;
z |= ((uint64_t) (*s++)) << 8;
z |= ((uint64_t) (*s++)) << 16;
z |= ((uint64_t) (*s++)) << 24;
z |= ((uint64_t) (*s++)) << 32;
z |= ((uint64_t) (*s++)) << 40;
z |= ((uint64_t) (*s++)) << 48;
z |= ((uint64_t) (*s++)) << 56;
return z;
}
static uint64_t uint64_t_load_bigendian(const unsigned char *s) {
uint64_t z = 0;
z |= ((uint64_t) (*s++)) << 56;
z |= ((uint64_t) (*s++)) << 48;
z |= ((uint64_t) (*s++)) << 40;
z |= ((uint64_t) (*s++)) << 32;
z |= ((uint64_t) (*s++)) << 24;
z |= ((uint64_t) (*s++)) << 16;
z |= ((uint64_t) (*s++)) << 8;
z |= ((uint64_t) (*s++)) << 0;
return z;
}
#include <time.h>
int clock_gettime(clockid_t clockid, struct timespec *tp)
{
tp->tv_sec = 0;
tp->tv_nsec = 0;
return 0;
}
int gettimeofday(struct timeval *tv,void *tz)
{
tv->tv_sec = 0;
tv->tv_usec = 0;
return 0;
}
#define PAGE 4096
#define ELF_CLASS_32 1
#define ELF_CLASS_64 2
// not all of these are supported yet
#define ELF_SPARC32 2
#define ELF_X86 3
#define ELF_MIPS32 8
#define ELF_SPARC32PLUS 18
#define ELF_PPC32 20
#define ELF_PPC64 21
#define ELF_ARM32 40
#define ELF_SPARC64 43
#define ELF_AMD64 62
#define ELF_ARM64 183
static char *fn = 0;
static int fd = -1;
static char *elf = 0;
static long long elf_len = 0;
static uint64_t elf_class = 0;
static uint64_t elf_endianness = 0;
static uint64_t elf_type = 0;
static uint64_t elf_machine = 0;
static uint64_t elf_version = 0;
static uint64_t elf_entry = 0;
static uint64_t elf_phoff = 0;
static uint64_t elf_flags = 0;
static uint64_t elf_phentsize = 0;
static uint64_t elf_phnum = 0;
static uint64_t elf_minaddr = 0;
static uint64_t elf_maxaddr = 0;
static uint8_t load8(long long pos)
{
if (pos < 0 || pos+1 > elf_len) {
fprintf(stderr,"%s misformatted, out-of-bounds read\n",fn);
exit(111);
}
return elf[pos];
}
static uint16_t load16(long long pos)
{
if (pos < 0 || pos+2 > elf_len) {
fprintf(stderr,"%s misformatted, out-of-bounds read\n",fn);
exit(111);
}
if (elf_endianness == 2) return uint16_t_load_bigendian((unsigned char *) elf+pos);
return uint16_t_load((unsigned char *) elf+pos);
}
static uint32_t load32(long long pos)
{
if (pos < 0 || pos+4 > elf_len) {
fprintf(stderr,"%s misformatted, out-of-bounds read\n",fn);
exit(111);
}
if (elf_endianness == 2) return uint32_t_load_bigendian((unsigned char *) elf+pos);
return uint32_t_load((unsigned char *) elf+pos);
}
static uint64_t load64(long long pos)
{
if (pos < 0 || pos+8 > elf_len) {
fprintf(stderr,"%s misformatted, out-of-bounds read\n",fn);
exit(111);
}
if (elf_endianness == 2) return uint64_t_load_bigendian((unsigned char *) elf+pos);
return uint64_t_load((unsigned char *) elf+pos);
}
static uint64_t ato64(const char *s)
{
uint64_t result = 0;
while (*s) result = result*10 + (*s++-'0');
return result;
}
static void elf_load(const char *fn,const char *bytes)
{
long long pos;
fd = open(fn,O_RDONLY);
if (fd == -1) {
fprintf(stderr,"unable to open %s: %s\n",fn,strerror(errno));
exit(111);
}
elf_len = ato64(bytes);
elf = malloc(elf_len);
if (!elf) {
fprintf(stderr,"unable to malloc %lld bytes: %s\n",elf_len,strerror(errno));
exit(111);
}
for (pos = 0;pos < elf_len;) {
long long r = read(fd,elf+pos,elf_len-pos);
if (r < 0) {
fprintf(stderr,"unable to read %s: %s\n",fn,strerror(errno));
exit(111);
}
if (r == 0) {
fprintf(stderr,"%s has been truncated, stopping\n",fn);
exit(111);
}
pos += r;
}
if (load8(0) != 127 || load8(1) != 'E' || load8(2) != 'L' || load8(3) != 'F') {
fprintf(stderr,"%s is not an ELF file, stopping\n",fn);
exit(100);
}
elf_class = load8(4);
if (elf_class != ELF_CLASS_32 && elf_class != ELF_CLASS_64) {
fprintf(stderr,"%s has unknown class %d, stopping\n",fn,(int) elf_class);
exit(100);
}
elf_endianness = load8(5);
if (elf_endianness < 1 || elf_endianness > 2) {
fprintf(stderr,"%s has unknown endianness %d, stopping\n",fn,(int) elf_endianness);
exit(100);
}
if (load8(6) != 1) {
fprintf(stderr,"%s has unknown header version %d, stopping\n",fn,(int) load8(6));
exit(100);
}
// XXX: check osabi in load8(7)
// XXX: check abi in load8(8)
elf_type = load16(16);
if (elf_type != 2) {
fprintf(stderr,"%s is not a static executable, stopping\n",fn);
exit(100);
}
elf_machine = load16(18);
elf_version = load32(20);
if (elf_version != 1) {
fprintf(stderr,"%s has unknown version %d, stopping\n",fn,(int) elf_version);
exit(100);
}
if (elf_class == ELF_CLASS_64) {
elf_entry = load64(24);
elf_phoff = load64(32);
elf_flags = load32(48);
elf_phentsize = load16(54);
elf_phnum = load16(56);
} else {
elf_entry = load32(24);
elf_phoff = load32(28);
elf_flags = load32(36);
elf_phentsize = load16(42);
elf_phnum = load16(44);
}
if (!elf_entry) {
fprintf(stderr,"%s has no entry point, stopping\n",fn);
exit(100);
}
if (!elf_phoff) {
fprintf(stderr,"%s has no program header, stopping\n",fn);
exit(100);
}
if (!elf_phnum) {
fprintf(stderr,"%s has no program-header entries, stopping\n",fn);
exit(100);
}
if (elf_phentsize != ((elf_class == ELF_CLASS_64) ? 56 : 32)) {
fprintf(stderr,"%s has wrong program-header-entry size %lld, stopping\n",fn,(long long) elf_phentsize);
exit(100);
}
elf_minaddr = 0;
elf_minaddr -= 1;
for (pos = 0;pos < elf_phnum;++pos) {
uint64_t h = elf_phoff+pos*elf_phentsize;
uint64_t p_type = load32(h);
uint64_t p_memsz = (elf_class == ELF_CLASS_64) ? load64(h+40) : load32(h+20);
uint64_t p_vaddr = (elf_class == ELF_CLASS_64) ? load64(h+16) : load32(h+8);
if (p_type == 1) { // PT_LOAD
if (p_vaddr < elf_minaddr) elf_minaddr = p_vaddr;
if (p_vaddr+p_memsz > elf_maxaddr) elf_maxaddr = p_vaddr+p_memsz;
}
}
if (elf_minaddr+1 == 0) {
fprintf(stderr,"%s has no segments to load, stopping\n",fn);
exit(100);
}
elf_minaddr = elf_minaddr & ~(PAGE-1);
elf_maxaddr = (elf_maxaddr+(PAGE-1)) & ~(PAGE-1);
if (print_elf) {
fprintf(stderr,"minaddr 0x%llx maxaddr 0x%llx\n",(long long) elf_minaddr,(long long) elf_maxaddr);
}
}
// =====
#include <unicorn/unicorn.h>
static uc_engine *uc;
static long long handling_trap;
static uint32_t handling_trap_num;
static uc_hook trace_interrupt;
static uc_hook trace_syscall;
static long long sim_brk;
static long long sim_sp;
static long long sim_maps;
static char sim_iobuf[65536];
static void hook_syscall(uc_engine *uc,void *user_data)
{
handling_trap = 1;
handling_trap_num = 0;
uc_emu_stop(uc);
}
static void hook_interrupt(uc_engine *uc,int32_t num,void *user_data)
{
handling_trap = 2;
handling_trap_num = num;
uc_emu_stop(uc);
}
uint64_t sim_read(uint64_t fd,uint64_t buf,uint64_t len)
{
uint64_t result;
// XXX: look at fd
if (len > sizeof sim_iobuf) len = sizeof sim_iobuf;
result = fread(sim_iobuf,1,len,stdin);
uc_mem_write(uc,buf,sim_iobuf,result);
return result;
}
uint64_t sim_write(uint64_t fd,uint64_t buf,uint64_t len)
{
// XXX: look at fd
if (len > sizeof sim_iobuf) len = sizeof sim_iobuf;
uc_mem_read(uc,buf,sim_iobuf,len);
fwrite(sim_iobuf,len,1,stdout);
fflush(stdout);
return len;
}
// XXX: extend interface+implementation for flags etc.
uint64_t sim_mmap(uint64_t size)
{
uint64_t result;
if (size > 1073741824) { // XXX: configure
fprintf(stderr,"huge mmap, aborting\n");
exit(111);
}
size &= PAGE-1;
size += PAGE;
uc_err err = uc_mem_map(uc,sim_maps,size,UC_PROT_ALL);
if (err != UC_ERR_OK) {
fprintf(stderr,"uc_mem_map failed: %s\n",uc_strerror(err));
exit(111);
}
result = sim_maps;
sim_maps += size;
return result;
}
static void trap(void)
{
if (handling_trap == 1 && elf_machine == ELF_AMD64) {
uint64_t rax,rdi,rsi,rdx,result = 0;
uc_reg_read(uc,UC_X86_REG_RAX,&rax);
uc_reg_read(uc,UC_X86_REG_RDI,&rdi);
uc_reg_read(uc,UC_X86_REG_RSI,&rsi);
uc_reg_read(uc,UC_X86_REG_RDX,&rdx);
if (print_interrupts)
fprintf(stderr,"syscall rax 0x%llx rdi 0x%llx rsi 0x%llx rdx 0x%llx\n",(long long) rax,(long long) rdi,(long long) rsi,(long long) rdx);
switch(rax) {
case 0x3c: // exit code
exit(rdi);
case 0x00: // read fd, buf, count
result = sim_read(rdi,rsi,rdx);
break;
case 0x01: // write fd, buf, count
result = sim_write(rdi,rsi,rdx);
break;
}
uc_reg_write(uc,UC_X86_REG_RAX,&result);
} else if (handling_trap == 2 && elf_machine == ELF_X86) {
uint32_t eax,ebx,ecx,edx,result = 0;
uc_reg_read(uc,UC_X86_REG_EAX,&eax);
uc_reg_read(uc,UC_X86_REG_EBX,&ebx);
uc_reg_read(uc,UC_X86_REG_ECX,&ecx);
uc_reg_read(uc,UC_X86_REG_EDX,&edx);
if (print_interrupts)
fprintf(stderr,"interrupt %lld eax 0x%llx ebx 0x%llx ecx 0x%llx edx 0x%llx\n",(long long) handling_trap_num, (long long) eax,(long long) ebx,(long long) ecx,(long long) edx);
if (handling_trap_num != 128) exit(111); // not a syscall
switch(eax) {
case 0x01: // exit code
case 0xfc: // exit_group code
exit(ebx);
case 0x03: // read fd, buf, count
result = sim_read(ebx,ecx,edx);
break;
case 0x04: // write fd, buf, count
result = sim_write(ebx,ecx,edx);
break;
case 0xc0: // mmap2
result = sim_mmap(ebx);
break;
}
uc_reg_write(uc,UC_X86_REG_EAX,&result);
} else if (handling_trap == 2 && elf_machine == ELF_ARM64) {
uint64_t x8,x0,x1,x2,result = 0;
uc_reg_read(uc,UC_ARM64_REG_X0,&x0);
uc_reg_read(uc,UC_ARM64_REG_X1,&x1);
uc_reg_read(uc,UC_ARM64_REG_X2,&x2);
uc_reg_read(uc,UC_ARM64_REG_X8,&x8);
if (print_interrupts)
fprintf(stderr,"interrupt %lld x8 0x%llx x0 0x%llx x1 0x%llx x2 0x%llx\n",(long long) handling_trap_num,(long long) x8,(long long) x0,(long long) x1,(long long) x2);
if (handling_trap_num != 2) exit(111); // not a syscall
switch(x8) {
case 0x5d: // exit code
case 0x5e: // exit_group code
exit(x0);
case 0x3f: // read fd, buf, count
result = sim_read(x0,x1,x2);
break;
case 0x40: // write fd, buf, count
result = sim_write(x0,x1,x2);
break;
case 0xde: // mmap
result = sim_mmap(x1);
break;
}
uc_reg_write(uc,UC_ARM64_REG_X0,&result);
} else if (handling_trap == 2 && elf_machine == ELF_ARM32) {
uint32_t r7,r0,r1,r2,result = 0;
uc_reg_read(uc,UC_ARM_REG_R0,&r0);
uc_reg_read(uc,UC_ARM_REG_R1,&r1);
uc_reg_read(uc,UC_ARM_REG_R2,&r2);
uc_reg_read(uc,UC_ARM_REG_R7,&r7);
if (print_interrupts)
fprintf(stderr,"interrupt %lld r7 0x%llx r0 0x%llx r1 0x%llx r2 0x%llx\n",(long long) handling_trap_num,(long long) r7,(long long) r0,(long long) r1,(long long) r2);
if (handling_trap_num != 2) exit(111); // not a syscall
switch(r7) {
case 0x01: // exit code
exit(r0);
case 0x03: // read fd, buf, count
result = sim_read(r0,r1,r2);
break;
case 0x04: // write fd, buf, count
result = sim_write(r0,r1,r2);
break;
}
uc_reg_write(uc,UC_ARM_REG_R0,&result);
} else if (handling_trap == 2 && elf_machine == ELF_SPARC32) {
uint32_t g1,o0,o1,o2,reg,result = 0;
uc_reg_read(uc,UC_SPARC_REG_G1,&g1);
uc_reg_read(uc,UC_SPARC_REG_O0,&o0);
uc_reg_read(uc,UC_SPARC_REG_O1,&o1);
uc_reg_read(uc,UC_SPARC_REG_O2,&o2);
if (print_interrupts)
fprintf(stderr,"interrupt %lld g1 0x%llx o0 0x%llx o1 0x%llx o2 0x%llx\n",(long long) handling_trap_num,(long long) g1,(long long) o0,(long long) o1,(long long) o2);
if (handling_trap_num != 144) exit(111); // not a syscall
switch (g1) {
case 0x01: // exit
exit(o0);
case 0x03: // read fd, buf, count
result = sim_read(o0,o1,o2);
break;
case 0x04: // write fd, buf, count
result = sim_write(o0,o1,o2);
break;
case 0x47: // mmap
result = sim_mmap(o1);
break;
}
uc_reg_read(uc,UC_SPARC_REG_PSR,®);
reg &= ~0x00100000; // carry bit of PSR would indicate error
uc_reg_write(uc,UC_SPARC_REG_PSR,®);
uc_reg_read(uc,UC_SPARC_REG_PC,®);
reg += 4; // move past the trap instruction
uc_reg_write(uc,UC_SPARC_REG_PC,®);
uc_reg_write(uc,UC_SPARC_REG_O0,&result);
} else {
if (print_interrupts) {
if (handling_trap == 1)
fprintf(stderr,"syscall, unhandled machine type\n");
else
fprintf(stderr,"interrupt %lld, unhandled machine type\n",(long long) handling_trap_num);
}
exit(111);
}
}
static void emulate(void)
{
uc_err err;
uint64_t startinsn;
uint32_t startinsn32;
long long pos;
switch(elf_machine) {
case ELF_SPARC32:
err = uc_open(UC_ARCH_SPARC,UC_MODE_SPARC32|UC_MODE_BIG_ENDIAN,&uc); break;
case ELF_X86:
err = uc_open(UC_ARCH_X86,UC_MODE_32,&uc); break;
case ELF_AMD64:
err = uc_open(UC_ARCH_X86,UC_MODE_64,&uc); break;
case ELF_ARM32:
err = uc_open(UC_ARCH_ARM,UC_MODE_ARM,&uc); break;
case ELF_ARM64:
err = uc_open(UC_ARCH_ARM64,UC_MODE_ARM,&uc); break;
case ELF_PPC64:
// XXX: as of 2025-02, unicorn does not support PPC64
// err = uc_open(UC_ARCH_PPC,UC_MODE_PPC64,&uc); break;
default:
fprintf(stderr,"unsupported machine type %lld\n",(long long) elf_machine);
exit(100);
}
if (err != UC_ERR_OK) {
fprintf(stderr,"uc_open failed: %s\n",uc_strerror(err));
exit(111);
}
sim_brk = elf_maxaddr;
sim_sp = sim_brk+1048576; // XXX: allow configuration
sim_maps = sim_sp+1048576; // XXX: allow configuration
if (elf_minaddr >= PAGE) {
err = uc_mem_map(uc,0,PAGE,UC_PROT_ALL);
if (err != UC_ERR_OK) {
fprintf(stderr,"uc_mem_map failed: %s\n",uc_strerror(err));
exit(111);
}
}
err = uc_mem_map(uc,elf_minaddr,sim_maps-elf_minaddr,UC_PROT_ALL);
if (err != UC_ERR_OK) {
fprintf(stderr,"uc_mem_map failed: %s\n",uc_strerror(err));
exit(111);
}
for (pos = 0;pos < elf_phnum;++pos) {
uint64_t h = elf_phoff+pos*elf_phentsize;
uint64_t p_type = load32(h);
uint64_t p_offset = (elf_class == ELF_CLASS_64) ? load64(h+8) : load32(h+4);
uint64_t p_vaddr = (elf_class == ELF_CLASS_64) ? load64(h+16) : load32(h+8);
uint64_t p_filesz = (elf_class == ELF_CLASS_64) ? load64(h+32) : load32(h+16);
if (p_type == 1) { // PT_LOAD
if (p_offset > elf_len || p_filesz > elf_len-p_offset) {
fprintf(stderr,"segment outside binary, stopping\n");
exit(111);
}
err = uc_mem_write(uc,p_vaddr,elf+p_offset,p_filesz);
if (err != UC_ERR_OK) {
fprintf(stderr,"uc_mem_write failed: %s\n",uc_strerror(err));
exit(111);
}
}
}
// XXX: could pass args along here
if (elf_class == ELF_CLASS_64) {
pos = 0;
uc_mem_write(uc,sim_sp-8,&pos,8);
uc_mem_write(uc,sim_sp-16,&pos,8);
uc_mem_write(uc,sim_sp-32,&pos,8);
pos = sim_sp-8;
uc_mem_write(uc,sim_sp-24,&pos,8);
uc_mem_write(uc,sim_sp-40,&pos,8);
pos = 1;
uc_mem_write(uc,sim_sp-48,&pos,8);
sim_sp -= 48;
} else {
pos = 0;
uc_mem_write(uc,sim_sp-4,&pos,4);
uc_mem_write(uc,sim_sp-8,&pos,4);
uc_mem_write(uc,sim_sp-16,&pos,4);
pos = sim_sp-4;
uc_mem_write(uc,sim_sp-12,&pos,4);
uc_mem_write(uc,sim_sp-20,&pos,4);
pos = 1;
uc_mem_write(uc,sim_sp-24,&pos,4);
sim_sp -= 24;
}
uint32_t sp32 = sim_sp;
uint32_t zero = 0;
switch(elf_machine) {
case ELF_X86:
uc_reg_write(uc,UC_X86_REG_ESP,&sim_sp); break;
case ELF_AMD64:
uc_reg_write(uc,UC_X86_REG_RSP,&sim_sp); break;
case ELF_ARM32:
uc_reg_write(uc,UC_ARM_REG_SP,&sp32); break;
case ELF_ARM64:
uc_reg_write(uc,UC_ARM64_REG_SP,&sim_sp); break;
case ELF_SPARC32:
uc_reg_write(uc,UC_SPARC_REG_SP,&sp32); break;
uc_reg_write(uc,UC_SPARC_REG_L7,&zero); break;
}
if (elf_machine == ELF_AMD64) {
uc_hook_add(uc,&trace_syscall,UC_HOOK_INSN,(void *)hook_syscall,0,1,0,UC_X86_INS_SYSCALL);
} else {
uc_hook_add(uc,&trace_interrupt,UC_HOOK_INTR,(void *)hook_interrupt,0,1,0);
}
startinsn = elf_entry;
for (;;) {
handling_trap = 0;
handling_trap_num = 0;
err = uc_emu_start(uc,startinsn,elf_maxaddr,0,0);
if (err != UC_ERR_OK) {
fprintf(stderr,"uc_emu_start failed: %s\n",uc_strerror(err));
exit(111);
}
if (!handling_trap) break;
trap();
switch(elf_machine) {
case ELF_X86:
uc_reg_read(uc,UC_X86_REG_EIP,&startinsn32);
startinsn = startinsn32; break;
case ELF_AMD64:
uc_reg_read(uc,UC_X86_REG_RIP,&startinsn); break;
case ELF_ARM32:
uc_reg_read(uc,UC_ARM_REG_PC,&startinsn32);
startinsn = startinsn32; break;
case ELF_ARM64:
uc_reg_read(uc,UC_ARM64_REG_PC,&startinsn); break;
case ELF_SPARC32:
uc_reg_read(uc,UC_SPARC_REG_PC,&startinsn32);
startinsn = startinsn32; break;
}
}
}
int main(int argc,char **argv)
{
if (!argv[1] || !argv[2]) {
fprintf(stderr,"need path to binary and number of bytes to read\n");
exit(100);
}
elf_load(argv[1],argv[2]);
emulate();
return 0;
}