From a2fa417af9bdbc5658c04fa8d061c46d5bca8511 Mon Sep 17 00:00:00 2001 From: John Reiser Date: Tue, 26 Nov 2024 16:43:02 -0800 Subject: [PATCH] Unify 64-bit upx_main() stub for shlib with 32-bit i386 modified: stub/src/amd64-linux.elf-so_main.c --- src/stub/src/amd64-linux.elf-so_main.c | 245 ++++++++++++++----------- 1 file changed, 134 insertions(+), 111 deletions(-) diff --git a/src/stub/src/amd64-linux.elf-so_main.c b/src/stub/src/amd64-linux.elf-so_main.c index f486f467..5f7c33fb 100644 --- a/src/stub/src/amd64-linux.elf-so_main.c +++ b/src/stub/src/amd64-linux.elf-so_main.c @@ -40,7 +40,7 @@ extern size_t Pwrite(unsigned, void const *, size_t); extern void f_int3(int arg); -#define DEBUG 0 +#define DEBUG 1 #ifndef DEBUG //{ #define DEBUG 0 @@ -242,33 +242,51 @@ ERR_LAB extern int memfd_create(const char *name, unsigned int flags); +#define ElfW(sym) Elf64_ ## sym + +#define nullptr (void *)0 + +extern char *upx_mmap_and_fd( // x86_64 Android emulator of i386 is not faithful + void *ptr // desired address + , unsigned len // also pre-allocate space in file + , char *pathname // 0 ==> call get_upxfn_path, which stores if 1st time +); + #if defined(__x86_64__) //{ -static char * -make_hatch_x86_64( - Elf64_Phdr const *const phdr, +char * +make_hatch( + ElfW(Phdr) const *const phdr, char *next_unc, unsigned frag_mask ) { - char *hatch = 0; + unsigned *hatch = 0; + unsigned code[3] = { + 0x5e5f050f, // syscall; pop %arg1{%rdi}; pop %arg2{%rsi} + 0xff3e585a, // pop %arg3{%rdx}; pop %rax; notrack jmp + 0x909090e0 // *%rax; nop; nop; nop + }; DPRINTF("make_hatch %%p %%p %%x\\n", phdr, next_unc, frag_mask); if (phdr->p_type==PT_LOAD && phdr->p_flags & PF_X) { next_unc += phdr->p_memsz - phdr->p_filesz; // Skip over local .bss frag_mask &= -(long)next_unc; // bytes left on page - if (6 <= frag_mask) { - hatch = next_unc; - (( long *)hatch)[0] = 0x5e5f050f; // syscall; pop %arg1{%rdi}; pop %arg2{%rsi} - ((short *)hatch)[2] = 0xc35a; // pop %arg3{%rdx}; ret + if (sizeof(code) <= frag_mask) { + hatch = (unsigned *)next_unc; + hatch[0] = code[0]; + hatch[1] = code[1]; + hatch[2] = code[2]; } else { // Does not fit at hi end of .text, so must use a new page "permanently" - int mfd = memfd_create(addr_string("upx"), 0); // the directory entry - Pwrite(mfd, addr_string("\x0f\x05\x5f\x5e\x5a\xc3"), 6); - hatch = Pmap(0, 6, PROT_READ|PROT_EXEC, MAP_PRIVATE, mfd, 0); + unsigned long fdmap = (long)upx_mmap_and_fd((void *)0, sizeof(code), nullptr); + unsigned mfd = -1+ (0xfff& fdmap); + write(mfd, &code, sizeof(code)); + hatch = mmap((void *)(fdmap & ~0xffful), sizeof(code), + PROT_READ|PROT_EXEC, MAP_PRIVATE, mfd, 0); close(mfd); } } DPRINTF("hatch=%%p\\n", hatch); - return hatch; + return (char *)hatch; } #elif defined(__powerpc64__) //}{ static unsigned @@ -278,8 +296,8 @@ ORRX(unsigned ra, unsigned rs, unsigned rb) // or ra,rs,rb } static char * -make_hatch_ppc64( - Elf64_Phdr const *const phdr, +make_hatch( + ElfW(Phdr) const *const phdr, char *next_unc, unsigned frag_mask ) @@ -314,8 +332,8 @@ make_hatch_ppc64( } #elif defined(__aarch64__) //{ static char * -make_hatch_arm64( - Elf64_Phdr const *const phdr, +make_hatch( + ElfW(Phdr) const *const phdr, char *next_unc, unsigned frag_mask ) @@ -365,26 +383,24 @@ make_hatch_arm64( |(REP8(PROT_WRITE) & EXP8(PF_W)) \ ) >> ((pf & (PF_R|PF_W|PF_X))<<2) )) -#define nullptr (void *)0 - #undef PAGE_MASK static unsigned long -get_PAGE_MASK(void) // the mask which KEEPS the page, discards the offset +get_page_mask(void) // the mask which KEEPS the page, discards the offset { unsigned long rv = ~0xffful; // default to (PAGE_SIZE == 4KiB) int fd = openat(0, addr_string("/proc/self/auxv"), O_RDONLY, 0); if (0 <= fd) { - Elf64_auxv_t data[40]; - Elf64_auxv_t *end = &data[read(fd, data, sizeof(data)) / sizeof(data[0])]; + ElfW(auxv_t) data[40]; + ElfW(auxv_t) *end = &data[read(fd, data, sizeof(data)) / sizeof(data[0])]; close(fd); - Elf64_auxv_t *ptr; for (ptr = &data[0]; ptr < end ; ++ptr) { + ElfW(auxv_t) *ptr; for (ptr = &data[0]; ptr < end ; ++ptr) { if (AT_PAGESZ == ptr->a_type) { rv = (0u - ptr->a_un.a_val); break; } } } - DPRINTF("get_PAGE_MASK= %%p\\n", rv); + DPRINTF("get_page_mask= %%p\\n", rv); return rv; } @@ -410,12 +426,47 @@ underlay(unsigned size, char *ptr, unsigned len); // Exchange the bits with values 4 (PF_R, PROT_EXEC) and 1 (PF_X, PROT_READ) // Use table lookup into a PIC-string that pre-computes the result. -unsigned PF_to_PROT(Elf64_Phdr const *phdr) +unsigned PF_to_PROT(ElfW(Phdr) const *phdr) { return 7& addr_string("@\x04\x02\x06\x01\x05\x03\x07") [phdr->p_flags & (PF_R|PF_W|PF_X)]; } +unsigned +fini_SELinux( + unsigned size, + char *ptr, + ElfW(Phdr) const *phdr, + unsigned mfd, + ElfW(Addr) base +) +{ + if (phdr->p_flags & PF_X) { + // Map the contents of mfd as per *phdr. + Punmap(ptr, size); + Pmap(ptr, size, PF_to_PROT(phdr), MAP_FIXED|MAP_PRIVATE, mfd, 0); + close(mfd); + } + else { // easy + Pprotect( (char *)(phdr->p_vaddr + base), phdr->p_memsz, PF_to_PROT(phdr)); + } + return 0; +} + +unsigned +prep_SELinux(unsigned size, char *ptr, unsigned len) // returns mfd +{ + // Cannot set PROT_EXEC except via mmap() into a region (Linux "vma") + // that has never had PROT_WRITE. So use a Linux-only "memory file" + // to hold the contents. + char *val = upx_mmap_and_fd(ptr, size, nullptr); + unsigned mfd = 0xfff & (unsigned)(long)val; + val -= mfd; --mfd; + if (len) + Pwrite(mfd, ptr, len); // Save lo fragment of contents on first page. + return mfd; +} + typedef struct { long argc; char **argv; @@ -423,7 +474,7 @@ typedef struct { } So_args; typedef struct { - unsigned off_reloc; // distance back to &Elf64_Ehdr + unsigned off_reloc; // distance back to &ElfW(Ehdr) unsigned off_user_DT_INIT; unsigned off_xct_off; // where un-compressed bytes end unsigned off_info; // xct_off: {l_info; p_info; b_info; compressed data) @@ -437,10 +488,10 @@ void * upx_so_main( // returns &escape_hatch So_info *so_info, So_args *so_args, - Elf64_Ehdr *elf_tmp // scratch for Elf64_Ehdr and Elf64_Phdrs + ElfW(Ehdr) *elf_tmp // scratch for ElfW(Ehdr) and ElfW(Phdrs) ) { - unsigned long const PAGE_MASK = get_PAGE_MASK(); + unsigned long const page_mask = get_page_mask(); char *const va_load = (char *)&so_info->off_reloc - so_info->off_reloc; So_info so_infc; // So_info Copy memcpy(&so_infc, so_info, sizeof(so_infc)); // before de-compression overwrites @@ -457,7 +508,7 @@ upx_so_main( // returns &escape_hatch // Copy compressed data before de-compression overwrites it. char *const sideaddr = mmap(nullptr, cpr_len, PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - DPRINTF("sideaddr=%%p\\n", sideaddr); + DPRINTF("&sideaddr=%%p\\n", &sideaddr); memcpy(sideaddr, cpr_ptr, cpr_len); // Transition to copied data @@ -479,110 +530,82 @@ upx_so_main( // returns &escape_hatch // De-compress from remaining [sideaddr, +sidelen). // Pprotect(,, PF_TO_PROT(.p_flags)); - // Get the uncompressed Elf64_Ehdr and Elf64_Phdr + // Get the uncompressed ElfW(Ehdr) and ElfW(Phdr) // The first b_info is aligned, so direct access to fields is OK. Extent x1 = {binfo->sz_unc, (char *)elf_tmp}; // destination Extent x0 = {binfo->sz_cpr + sizeof(*binfo), (char *)binfo}; // source unpackExtent(&x0, &x1); // de-compress _Ehdr and _Phdrs; x0.buf is updated - Elf64_Phdr const *phdr = (Elf64_Phdr *)(1+ elf_tmp); - Elf64_Phdr const *const phdrN = &phdr[elf_tmp->e_phnum]; - while (phdr->p_type != PT_LOAD) ++phdr; // skip PT_PHDR if any - Elf64_Addr const base = (Elf64_Addr)va_load - phdr->p_vaddr; - DPRINTF("base=%%p\\n", base); - - if (phdr->p_flags & PF_X) { - int mfd = memfd_create(addr_string("upx"), 0); - unsigned mfd_len = 0ul - PAGE_MASK; - Pwrite(mfd, elf_tmp, binfo->sz_unc); // de-compressed Elf_Ehdr and Elf_Phdrs - Pwrite(mfd, binfo->sz_unc + va_load, mfd_len - binfo->sz_unc); // rest of 1st page - - Punmap(va_load, mfd_len); // make SELinux forget any previous protection - Elf64_Addr va_mfd = (Elf64_Addr)Pmap(va_load, mfd_len, PF_to_PROT(phdr), - MAP_FIXED|MAP_PRIVATE, mfd, 0); (void)va_mfd; - close(mfd); - } + ElfW(Phdr) const *phdr = (ElfW(Phdr) *)(1+ elf_tmp); + ElfW(Phdr) const *const phdrN = &phdr[elf_tmp->e_phnum]; // Process each read-only PT_LOAD. // A read+write PT_LOAD might be relocated by rtld before de-compression, // so it cannot be compressed. - struct b_info al_bi; // for aligned data from binfo void *hatch = nullptr; + ElfW(Addr) base = 0; + int n_load = 0; for (; phdr < phdrN; ++phdr) - if ( phdr->p_type == PT_LOAD && !(phdr->p_flags & PF_W)) { - DPRINTF("phdr@%%p p_offset=%%p p_vaddr=%%p p_filesz=%%p p_memsz=%%p binfo=%%p\\n", - phdr, phdr->p_offset, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz, x0.buf); - - if ((phdr->p_filesz + phdr->p_offset) <= so_infc.off_xct_off) { - continue; // below compressed region - } - Elf64_Addr const pfx = (so_infc.off_xct_off < phdr->p_offset) - ? 0 // entire PT_LOAD is compressed - : so_infc.off_xct_off - phdr->p_offset ; // below xct_off is not + if (phdr->p_type == PT_LOAD && !(phdr->p_flags & PF_W)) { + unsigned hi_offset = phdr->p_filesz + phdr->p_offset; + struct b_info al_bi; // for aligned data from binfo + // Need un-aligned read of b_info to determine compression sizes. x0.size = sizeof(struct b_info); xread(&x0, (char *)&al_bi, x0.size); // aligned binfo x0.buf -= sizeof(al_bi); // back up (the xread() was a peek) - DPRINTF("next1 pfx=%%x binfo@%%p (%%p %%p %%p)\\n", pfx, x0.buf, - al_bi.sz_unc, al_bi.sz_cpr, *(unsigned *)(void *)&al_bi.b_method); + x1.size = hi_offset - xct_off; + x1.buf = (void *)(hi_offset + base - al_bi.sz_unc); + x0.size = al_bi.sz_cpr; - // Using .p_memsz implicitly handles .bss via MAP_ANONYMOUS. - // Omit any non-tcompressed prefix (below xct_off) - x1.buf = (char *)(pfx + phdr->p_vaddr + base); - x1.size = phdr->p_memsz - pfx; - - unsigned const frag = (phdr->p_vaddr + pfx) & ~PAGE_MASK; // lo fragment on page - x1.buf -= frag; - x1.size += frag; - DPRINTF("phdr(%%p %%p) xct_off=%%x frag=%%x\\n", x1.buf, x1.size, xct_off, frag); - - int mfd = 0; - char *mfd_addr = 0; - if (phdr->p_flags & PF_X) { // SELinux - // Cannot set PROT_EXEC except via mmap() into a region (Linux "vma") - // that has never had PROT_WRITE. So use a Linux-only "memory file" - // to hold the contents. - mfd = memfd_create(addr_string("upx"), 0); // the directory entry - ftruncate(mfd, x1.size); // Allocate the pages in the file. - Pwrite(mfd, x1.buf, frag); // Save lo fragment of contents on first page. - Punmap(x1.buf, x1.size); - mfd_addr = Pmap(x1.buf, x1.size, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, mfd, 0); - DPRINTF("mfd_addr= %%p\\n", mfd_addr); // Re-use the address space - } - else { - underlay(x1.size, x1.buf, frag); // also makes PROT_WRITE + if (!base) { + base = (ElfW(Addr))va_load - phdr->p_vaddr; + DPRINTF("base=%%p\\n", base); } + DPRINTF("phdr@%%p p_offset=%%p p_vaddr=%%p p_filesz=%%p p_memsz=%%p\\n", + phdr, phdr->p_offset, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz); + DPRINTF("x0=%%p x1=%%p\\n", &x0, &x1); + //my_bkpt((void const *)0x1230, phdr, &x0, &x1); - x1.buf += frag; - x1.size = al_bi.sz_unc; - x0.size = al_bi.sz_cpr + sizeof(struct b_info); - DPRINTF("before unpack x0=(%%p %%p x1=(%%p %%p)\\n", x0.size, x0.buf, x1.size, x1.buf); - unpackExtent(&x0, &x1); // updates x0 and x1 - DPRINTF(" after unpack x0=(%%p %%p x1=(%%p %%p)\\n", x0.size, x0.buf, x1.size, x1.buf); - - if (!hatch && phdr->p_flags & PF_X) { -#if defined(__x86_64) //{ - hatch = make_hatch_x86_64(phdr, x1.buf, ~PAGE_MASK); -#elif defined(__powerpc64__) //}{ - hatch = make_hatch_ppc64(phdr, x1.buf, ~PAGE_MASK); -#elif defined(__aarch64__) //}{ - hatch = make_hatch_arm64(phdr, x1.buf, ~PAGE_MASK); -#endif //} + unsigned frag = ~page_mask & (unsigned)(long)x1.buf; + unsigned mfd = 0; + if (!n_load) { // 1st PT_LOAD is special. + // Already ELF headers are in place, perhaps already followed + // by non-compressed loader tables below xct_off. + if (xct_off < hi_offset) { // 1st PT_LOAD also has compressed code, too + if (phdr->p_flags & PF_X) { + mfd = prep_SELinux(x1.size, x1.buf, frag); + } + else { + underlay(x1.size, x1.buf, page_mask); // also makes PROT_WRITE + } + unpackExtent(&x0, &x1); + if (!hatch && phdr->p_flags & PF_X) { + hatch = make_hatch(phdr, x1.buf, ~page_mask); + } + my_bkpt((void const *)0x1235, &x1); + fini_SELinux(x1.size, x1.buf, phdr, mfd, base); // FIXME: x1 changed! + } } - - if (phdr->p_flags & PF_X) { // SELinux - // Map the contents of mfd as per *phdr. - DPRINTF("mfd mmap addr=%%p len=%%p\\n", (phdr->p_vaddr + base + pfx), al_bi.sz_unc); - Punmap(mfd_addr, frag + al_bi.sz_unc); // Discard RW mapping; mfd has the bytes - Pmap((char *)(phdr->p_vaddr + base + pfx), al_bi.sz_unc, PF_to_PROT(phdr), - MAP_FIXED|MAP_PRIVATE, mfd, 0); - close(mfd); - } - else { // easy - Pprotect( (char *)(phdr->p_vaddr + base), phdr->p_memsz, PF_to_PROT(phdr)); + else { // 2nd and later PT_LOADs + x1.buf = (void *)(phdr->p_vaddr + base); + x1.size = phdr->p_filesz; + if (phdr->p_flags & PF_X) { + mfd = prep_SELinux(x1.size, x1.buf, frag); + } + else { + underlay(x1.size, x1.buf, page_mask); // also makes PROT_WRITE + } + unpackExtent(&x0, &x1); + if (!hatch && phdr->p_flags &PF_X) { + hatch = make_hatch(phdr, x1.buf, ~page_mask); + } + fini_SELinux(al_bi.sz_unc, (void *)(phdr->p_vaddr + base), phdr, mfd, base); } + ++n_load; } + DPRINTF("Punmap sideaddr=%%p cpr_len=%%p\\n", sideaddr, cpr_len); Punmap(sideaddr, cpr_len); DPRINTF("calling user DT_INIT %%p\\n", dt_init); dt_init(so_args->argc, so_args->argv, so_args->envp);