From 82ed25bb44d19fc170bfc2c44dddd4669339b502 Mon Sep 17 00:00:00 2001 From: John Reiser Date: Fri, 14 Oct 2022 15:53:55 -0700 Subject: [PATCH] Major fix 64-bit un_shlib_1() and unpack(), but not --android-shlib Also canUnpack() is really a 'bool' which sets overlay_offset when 'true'; confusing! https://github.com/upx/upx/issues/599 (partial) modified: p_lx_elf.cpp modified: p_lx_elf.h modified: p_unix.h --- src/p_lx_elf.cpp | 273 +++++++++++++++++++++++++---------------------- src/p_lx_elf.h | 8 +- src/p_unix.h | 2 +- 3 files changed, 148 insertions(+), 135 deletions(-) diff --git a/src/p_lx_elf.cpp b/src/p_lx_elf.cpp index b5c55d62..527d178a 100644 --- a/src/p_lx_elf.cpp +++ b/src/p_lx_elf.cpp @@ -1978,7 +1978,7 @@ bool PackLinuxElf32::calls_crt1(Elf32_Rel const *rel, int sz) #include "p_elf_enum.h" #undef WANT_REL_ENUM -int PackLinuxElf32::canUnpack() +int PackLinuxElf32::canUnpack() // really 'bool' { if (checkEhdr(&ehdri)) { return false; @@ -2409,7 +2409,7 @@ proceed: ; return true; } -int PackLinuxElf64::canUnpack() +int PackLinuxElf64::canUnpack() // really 'bool' { if (checkEhdr(&ehdri)) { return false; @@ -2428,7 +2428,7 @@ int PackLinuxElf64::canUnpack() fi->seek(filesz+offset, SEEK_SET); MemBuffer buf(32 + sizeof(overlay_offset)); fi->readx(buf, buf.getSize()); - unsigned x = PackUnix::find_overlay_offset(buf); + bool x = PackUnix::find_overlay_offset(buf); if (x) { return x; } @@ -3406,7 +3406,11 @@ void PackLinuxElf32::pack1(OutputFile *fo, Filter & /*ft*/) unsigned sh_size = get_te32(&shdr->sh_size); unsigned sh_offset = get_te32(&shdr->sh_offset); unsigned sh_entsize = get_te32(&shdr->sh_entsize); - if (xct_off <= sh_offset) { + unsigned sh_flags = get_te32(&shdr->sh_flags); + if (xct_off <= sh_offset + // Omit .comment (0==.sh_addr; !SHF_ALLOC) etc. + && (shdr->sh_addr || Elf32_Shdr::SHF_ALLOC & sh_flags) + ) { //set_te32(&shdr->sh_offset, asl_delta + sh_offset); // FIXME ?? upx_uint32_t addr = get_te32(&shdr->sh_addr); set_te32(&shdr->sh_addr, asl_delta + addr); @@ -4891,72 +4895,68 @@ void PackLinuxElf64::un_shlib_1( unpackExtent(ph.u_len, fo, c_adler, u_adler, false, szb_info); - // Recover original Elf headers from current output file - InputFile u_fi; + + // FIXME: what if no output file? test mode ("-t") or list mode ("-l") if (fo) { + InputFile u_fi; + // Recover original Elf headers from current output file u_fi.open(fo->getName(), 0); u_fi.readx((void *)o_elfhdrs,o_elfhdrs.getSize()); u_fi.close(); + + // Re-generate unmodified rtld data below xct_off + fo->write(&ibuf[ph.u_len], xct_off - ph.u_len); } - Elf64_Phdr const *t_phdr = (Elf64_Phdr const *)(1+ (Elf64_Ehdr const *)(void const *)o_elfhdrs); - unsigned t_flags = get_te32(&t_phdr->p_flags); - - // New style: 1st PT_LOAD has no eXecutable code - if (!(Elf64_Phdr::PF_X & t_flags)) { - if (fo) { - fo->write(&ibuf[ph.u_len], xct_off - ph.u_len); + Elf64_Phdr const *o_phdr = (Elf64_Phdr const *)(1+ (Elf64_Ehdr const *)(void const *)o_elfhdrs); + // Handle compressed PT_LOADs (must not have PF_W) + for (unsigned j = 0; j < e_phnum; ++j, ++o_phdr) { + unsigned type = get_te32(&o_phdr->p_type); + unsigned flags = get_te32(&o_phdr->p_flags); + if (PT_LOAD64 != type || Elf64_Phdr::PF_W & flags) { + continue; } - // Now handle compressed PT_LOADs. - // Phdr are parallel from input to output, except perhaps PT_NULL. - Elf64_Phdr const *i_phdr = phdri; - Elf64_Phdr const *o_phdr = (Elf64_Phdr const *)(1+ (Elf64_Ehdr const *)(void const *)o_elfhdrs); - int once = 0; - for (unsigned k = 0; k < e_phnum; ++k, ++i_phdr, ++o_phdr) { - unsigned type = get_te32(&o_phdr->p_type); // output side avoids PT_NULL - unsigned flags = get_te32(&o_phdr->p_flags); - unsigned vaddr = get_te64(&o_phdr->p_vaddr); - unsigned filesz = get_te64(&o_phdr->p_filesz); - unsigned o_offset = get_te64(&o_phdr->p_offset); - unsigned i_offset = get_te64(&i_phdr->p_offset); - if (xct_off <= vaddr) { // beyond rtld control info - if (PT_LOAD64==type - && xct_off < (filesz + vaddr)) { - if (!(Elf64_Phdr::PF_W & flags)) { // Read-only, so was compressed - fi->readx(&hdr.b, sizeof(hdr.b)); - fi->seek(-(off_t)sizeof(struct b_info), SEEK_CUR); - if (fo) { - fo->seek(o_offset, SEEK_SET); - } - ph.c_len = get_te32(&hdr.b.sz_cpr); - ph.u_len = get_te32(&hdr.b.sz_unc); - unpackExtent(ph.u_len, fo, c_adler, u_adler, false, szb_info); - } - else { // Writeable, so might written by rtld, so not compressed. - if (!once++) { - funpad4(fi); - loader_offset = fi->tell(); - } - fi->seek(i_offset, SEEK_SET); - fi->readx(ibuf, filesz); - total_in += filesz; - if (fo) { - fo->seek(o_offset, SEEK_SET); - fo->write(ibuf, filesz); - } - total_out = filesz + o_offset; // high-water mark - } - } + unsigned vaddr = get_te64(&o_phdr->p_vaddr); + if (xct_off <= vaddr) { // not first PT_LOAD must position its output + if (fo) { + unsigned o_offset = get_te64(&o_phdr->p_offset); + fo->seek(o_offset, SEEK_SET); } } + // Peek at b_info to find sizes + fi->readx(&hdr.b, sizeof(hdr.b)); + fi->seek(-(off_t)sizeof(struct b_info), SEEK_CUR); + ph.c_len = get_te32(&hdr.b.sz_cpr); + ph.u_len = get_te32(&hdr.b.sz_unc); + unpackExtent(ph.u_len, fo, c_adler, u_adler, false, szb_info); } - else { // Old style - if (fo) { - Elf64_Phdr const *o_phdr = (Elf64_Phdr const *)(1+ (Elf64_Ehdr const *)(void const *)o_elfhdrs); - fi->seek(o_phdr->p_offset, SEEK_CUR); // DEBUG no-op + funpad4(fi); + loader_offset = fi->tell(); + + // Handle PT_LOAD with PF_W: writeable, so not compressed. "Slide" + o_phdr = (Elf64_Phdr const *)(1+ (Elf64_Ehdr const *)(void const *)o_elfhdrs); + Elf64_Phdr const *i_phdr = phdri; + for (unsigned j = 0; j < e_phnum; ++j, ++o_phdr, ++i_phdr) { + unsigned type = get_te32(&o_phdr->p_type); + unsigned flags = get_te32(&o_phdr->p_flags); + if (PT_LOAD64 != type || !(Elf64_Phdr::PF_W & flags)) { + continue; } + unsigned filesz = get_te64(&o_phdr->p_filesz); + unsigned o_offset = get_te64(&o_phdr->p_offset); + unsigned i_offset = get_te64(&i_phdr->p_offset); + fi->seek(i_offset, SEEK_SET); + fi->readx(ibuf, filesz); + total_in += filesz; + if (fo) { + fo->seek(o_offset, SEEK_SET); + fo->write(ibuf, filesz); + } + total_out = filesz + o_offset; // high-water mark } + // Gaps between PT_LOAD will be handled by ::unpack() + // position fi at loader offset fi->seek(loader_offset, SEEK_SET); } @@ -4971,79 +4971,92 @@ void PackLinuxElf64::un_DT_INIT( { // DT_INIT must be restored. // If android_shlib, then the asl_delta relocations must be un-done. - upx_uint64_t dt_pltrelsz(0), dt_jmprel(0); - upx_uint64_t dt_relasz(0), dt_rela(0); - upx_uint64_t const dyn_len = get_te64(&dynhdr->p_filesz); - upx_uint64_t const dyn_off = get_te64(&dynhdr->p_offset); - if ((unsigned long)file_size < (dyn_len + dyn_off)) { - char msg[50]; snprintf(msg, sizeof(msg), - "bad PT_DYNAMIC .p_filesz %#lx", (long unsigned)dyn_len); - throwCantUnpack(msg); - } - fi->seek(dyn_off, SEEK_SET); - fi->readx(ibuf, dyn_len); - Elf64_Dyn *dyn = (Elf64_Dyn *)(void *)ibuf; - dynseg = dyn; invert_pt_dynamic(dynseg, - umin(dyn_len, file_size - dyn_off)); - for (unsigned j2= 0; j2 < dyn_len; ++dyn, j2 += sizeof(*dyn)) { - upx_uint64_t const tag = get_te64(&dyn->d_tag); - upx_uint64_t val = get_te64(&dyn->d_val); - if (is_asl) switch (tag) { - case Elf64_Dyn::DT_RELASZ: { dt_relasz = val; } break; - case Elf64_Dyn::DT_RELA: { dt_rela = val; } break; - case Elf64_Dyn::DT_PLTRELSZ: { dt_pltrelsz = val; } break; - case Elf64_Dyn::DT_JMPREL: { dt_jmprel = val; } break; + upx_uint64_t dt_pltrelsz(0), dt_jmprel(0); + upx_uint64_t dt_relasz(0), dt_rela(0); + upx_uint64_t const dyn_len = get_te64(&dynhdr->p_filesz); + upx_uint64_t const dyn_off = get_te64(&dynhdr->p_offset); + if ((unsigned long)file_size < (dyn_len + dyn_off)) { + char msg[50]; snprintf(msg, sizeof(msg), + "bad PT_DYNAMIC .p_filesz %#lx", (long unsigned)dyn_len); + throwCantUnpack(msg); + } + fi->seek(dyn_off, SEEK_SET); + fi->readx(ibuf, dyn_len); + Elf64_Dyn *dyn = (Elf64_Dyn *)(void *)ibuf; + dynseg = dyn; invert_pt_dynamic(dynseg, + umin(dyn_len, file_size - dyn_off)); + for (unsigned j2= 0; j2 < dyn_len; ++dyn, j2 += sizeof(*dyn)) { + upx_uint64_t const tag = get_te64(&dyn->d_tag); + upx_uint64_t val = get_te64(&dyn->d_val); + if (is_asl) switch (tag) { + case Elf64_Dyn::DT_RELASZ: { dt_relasz = val; } break; + case Elf64_Dyn::DT_RELA: { dt_rela = val; } break; + case Elf64_Dyn::DT_PLTRELSZ: { dt_pltrelsz = val; } break; + case Elf64_Dyn::DT_JMPREL: { dt_jmprel = val; } break; - case Elf64_Dyn::DT_PLTGOT: - case Elf64_Dyn::DT_PREINIT_ARRAY: - case Elf64_Dyn::DT_INIT_ARRAY: - case Elf64_Dyn::DT_FINI_ARRAY: - case Elf64_Dyn::DT_FINI: { - set_te64(&dyn->d_val, val - asl_delta); - }; break; - } // end switch() on tag when is_asl - if (upx_dt_init == tag) { - if (Elf64_Dyn::DT_INIT == tag) { - set_te64(&dyn->d_val, old_dtinit); - if (!old_dtinit) { // compressor took the slot - dyn->d_tag = Elf64_Dyn::DT_NULL; - dyn->d_val = 0; - } - } - else if (Elf64_Dyn::DT_INIT_ARRAY == tag - || Elf64_Dyn::DT_PREINIT_ARRAY == tag) { - if (val < load_va || (long unsigned)file_size < (long unsigned)val) { - char msg[50]; snprintf(msg, sizeof(msg), - "Bad Dynamic tag %#lx %#lx", - (long unsigned)tag, (long unsigned)val); - throwCantUnpack(msg); - } - set_te64(&ibuf[val - load_va], old_dtinit - + (is_asl ? asl_delta : 0)); // counter-act unRel64 - } - } - } - if (fo) { // Write updated dt_*.val - upx_uint64_t dyn_offo = get_te64(&phdro[dynhdr - phdri].p_offset); - fo->seek(dyn_offo, SEEK_SET); - fo->rewrite(ibuf, dyn_len); - } - if (is_asl) { - lowmem.alloc(xct_off); - fi->seek(0, SEEK_SET); - fi->read(lowmem, xct_off); // contains relocation tables - if (dt_relasz && dt_rela) { - Elf64_Rela *const rela0 = (Elf64_Rela *)lowmem.subref( - "bad Rela offset", dt_rela, dt_relasz); - unRela64(dt_rela, rela0, dt_relasz, ibuf, load_va, old_dtinit, fo); - } - if (dt_pltrelsz && dt_jmprel) { // FIXME: overlap w/ DT_REL ? - Elf64_Rela *const jmp0 = (Elf64_Rela *)lowmem.subref( - "bad Jmprel offset", dt_jmprel, dt_pltrelsz); - unRela64(dt_jmprel, jmp0, dt_pltrelsz, ibuf, load_va, old_dtinit, fo); - } - // Modified relocation tables are re-written by unRela64 - } + case Elf64_Dyn::DT_PLTGOT: + case Elf64_Dyn::DT_PREINIT_ARRAY: + case Elf64_Dyn::DT_INIT_ARRAY: + case Elf64_Dyn::DT_FINI_ARRAY: + case Elf64_Dyn::DT_FINI: { + set_te64(&dyn->d_val, val - asl_delta); + }; break; + } // end switch() on tag when is_asl + if (upx_dt_init == tag) { + if (Elf64_Dyn::DT_INIT == tag) { + set_te64(&dyn->d_val, old_dtinit); + if (!old_dtinit) { // compressor took the slot + dyn->d_tag = Elf64_Dyn::DT_NULL; + dyn->d_val = 0; + } + } + else if (Elf64_Dyn::DT_INIT_ARRAY == tag + || Elf64_Dyn::DT_PREINIT_ARRAY == tag) { + // The slot must have a R_*_RELATIVE relocation (is_shlib, + // after all), but ElfXX_Rela ignores the initial contents! + // So changing the value will get ignored. Do it anyway. + // FIXME: we must fix the Rela ? + Elf64_Phdr const *phdr = phdro; + for (unsigned j = 0; j < e_phnum; ++j, ++phdr) { + upx_uint64_t vaddr = get_te64(&phdr->p_vaddr); + upx_uint64_t filesz = get_te64(&phdr->p_filesz); + if ((val - vaddr) < filesz) { + upx_uint64_t offset = get_te64(&phdr->p_offset); + upx_uint64_t oldval; + // Counter-act unRel64 if asl_delta + set_te64(&oldval, old_dtinit + (is_asl ? asl_delta : 0)); + // FIXME? the in-memory copy? + if (fo) { + fo->seek((val - vaddr) + offset, SEEK_SET); + fo->write(&oldval, sizeof(oldval)); + } + break; + } + } + } + } + } + if (fo) { // Write updated dt_*.val + upx_uint64_t dyn_offo = get_te64(&phdro[dynhdr - phdri].p_offset); + fo->seek(dyn_offo, SEEK_SET); + fo->rewrite(ibuf, dyn_len); + } + if (is_asl) { + lowmem.alloc(xct_off); + fi->seek(0, SEEK_SET); + fi->read(lowmem, xct_off); // contains relocation tables + if (dt_relasz && dt_rela) { + Elf64_Rela *const rela0 = (Elf64_Rela *)lowmem.subref( + "bad Rela offset", dt_rela, dt_relasz); + unRela64(dt_rela, rela0, dt_relasz, ibuf, load_va, old_dtinit, fo); + } + if (dt_pltrelsz && dt_jmprel) { // FIXME: overlap w/ DT_REL ? + Elf64_Rela *const jmp0 = (Elf64_Rela *)lowmem.subref( + "bad Jmprel offset", dt_jmprel, dt_pltrelsz); + unRela64(dt_jmprel, jmp0, dt_pltrelsz, ibuf, load_va, old_dtinit, fo); + } + // Modified relocation tables are re-written by unRela64 + } } void PackLinuxElf64::unpack(OutputFile *fo) @@ -5286,7 +5299,7 @@ PackLinuxElf32x86::~PackLinuxElf32x86() { } -int PackLinuxElf32x86::canUnpack() +int PackLinuxElf32x86::canUnpack() // really 'bool' { if (super::canUnpack()) { return true; diff --git a/src/p_lx_elf.h b/src/p_lx_elf.h index f2490755..5325a3ea 100644 --- a/src/p_lx_elf.h +++ b/src/p_lx_elf.h @@ -42,7 +42,7 @@ public: /*virtual void buildLoader(const Filter *);*/ virtual int getVersion() const override { return 14; } // upx-3.96 cannot upack, for instance virtual bool canUnpackVersion(int version) const override { return (version >= 11); } - virtual int canUnpack() override { return super::canUnpack(); } + virtual int canUnpack() override { return super::canUnpack(); } // really 'bool' protected: virtual const int *getCompressionMethods(int method, int level) const override; @@ -123,7 +123,7 @@ protected: virtual void PackLinuxElf32help1(InputFile *f); virtual int checkEhdr(Elf32_Ehdr const *ehdr) const; virtual bool canPack() override; - virtual int canUnpack() override; + virtual int canUnpack() override; // really 'bool' // These ARM routines are essentially common to big/little endian, // but the class hierarchy splits after this class. @@ -266,7 +266,7 @@ protected: virtual void PackLinuxElf64help1(InputFile *f); virtual int checkEhdr(Elf64_Ehdr const *ehdr) const; virtual bool canPack() override; - virtual int canUnpack() override; + virtual int canUnpack() override; // really 'bool' virtual void pack1(OutputFile *, Filter &) override; // generate executable header virtual int pack2(OutputFile *, Filter &) override; // append compressed data @@ -558,7 +558,7 @@ public: virtual const char *getName() const override { return "linux/i386"; } virtual const char *getFullName(const options_t *) const override { return "i386-linux.elf"; } virtual const int *getFilters() const override; - virtual int canUnpack() override; + virtual int canUnpack() override; // reallly 'bool' protected: virtual void pack1(OutputFile *, Filter &) override; // generate executable header diff --git a/src/p_unix.h b/src/p_unix.h index 2e90fc33..35739bb9 100644 --- a/src/p_unix.h +++ b/src/p_unix.h @@ -49,7 +49,7 @@ public: virtual void unpack(OutputFile *fo) override; virtual bool canPack() override; - virtual int canUnpack() override; + virtual int canUnpack() override; // really 'bool' int find_overlay_offset(MemBuffer const &buf); protected: