De-compression for 32-bit shared libraries on i686, ARM

https://github.com/upx/upx/issues/609
	modified:   p_lx_elf.cpp
	modified:   p_lx_elf.h
This commit is contained in:
John Reiser 2023-03-23 16:28:34 -07:00
parent 0bf8ff151e
commit 5d15e57294
2 changed files with 96 additions and 50 deletions

View File

@ -355,10 +355,10 @@ PackLinuxElf32::PackLinuxElf32help1(InputFile *f)
check_pt_load(phdr);
}
// elf_find_dynamic() returns 0 if 0==dynseg.
dynstr = (char const *)elf_find_dynamic(Elf32_Dyn::DT_STRTAB);
dynsym = (Elf32_Sym const *)elf_find_dynamic(Elf32_Dyn::DT_SYMTAB);
gashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_GNU_HASH);
hashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_HASH);
dynstr = (char const *)elf_find_dynamic(Elf32_Dyn::DT_STRTAB);
dynsym = (Elf32_Sym /*const*/ *)elf_find_dynamic(Elf32_Dyn::DT_SYMTAB);
gashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_GNU_HASH);
hashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_HASH);
if (3& ((upx_uintptr_t)dynsym | (upx_uintptr_t)gashtab | (upx_uintptr_t)hashtab)) {
throwCantPack("unaligned DT_SYMTAB, DT_GNU_HASH, or DT_HASH/n");
}
@ -420,7 +420,7 @@ off_t PackLinuxElf::pack3(OutputFile *fo, Filter &ft) // return length of output
// FIXME: debugging aid: entry to decompressor
if (lowmem.getSize()) {
Elf32_Ehdr *const ehdr = (Elf32_Ehdr *)&lowmem[0];
set_te32(&ehdr->e_entry, sz_pack2);
set_te32(&ehdr->e_entry, sz_pack2); // hint for decomperssor
}
// end debugging aid
@ -431,8 +431,8 @@ off_t PackLinuxElf::pack3(OutputFile *fo, Filter &ft) // return length of output
return total_out;
}
Elf32_Phdr *
PackLinuxElf32::elf_find_Phdr_for_va(upx_uint32_t addr, Elf32_Phdr *phdr, unsigned phnum)
Elf32_Phdr const *
PackLinuxElf32::elf_find_Phdr_for_va(upx_uint32_t addr, Elf32_Phdr const *phdr, unsigned phnum)
{
for (unsigned j = 0; j < phnum; ++phdr) {
if ((addr - get_te32(&phdr->p_vaddr)) < get_te32(&phdr->p_filesz)) {
@ -526,10 +526,10 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft)
set_te32(&elfout.ehdr.e_entry, abrk + get_te32(&elfout.ehdr.e_entry) - vbase);
}
if (0!=xct_off) { // shared library
unsigned word = (Elf32_Ehdr::EM_ARM==e_machine) + load_va + sz_pack2; // Thumb mode
set_te32(&file_image[user_init_off], word); // set the hook
unsigned const cpr_entry = (Elf32_Ehdr::EM_ARM==e_machine) + load_va + sz_pack2; // Thumb mode
set_te32(&file_image[user_init_off], cpr_entry); // set the hook
Elf32_Dyn *dynp = (Elf32_Dyn *)elf_find_dynptr(Elf32_Dyn::DT_NULL);
Elf32_Dyn *dynp = (Elf32_Dyn *)elf_find_dynptr(Elf32_Dyn::DT_NULL); // for decompressor
set_te32(&dynp->d_val, (char *)user_init_rp - (char *)&file_image[0]);
Elf32_Phdr *const phdr0 = (Elf32_Phdr *)lowmem.subref(
@ -564,9 +564,9 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft)
set_te32(&phdr->p_memsz, total_out - ioff);
if (user_init_off < xct_off) { // MIPS puts PT_DYNAMIC here
// Allow for DT_INIT in a new [stolen] slot
unsigned off2 = user_init_off - sizeof(word);
unsigned off2 = user_init_off - sizeof(unsigned);
fo->seek(off2, SEEK_SET);
fo->rewrite(&file_image[off2], 2*sizeof(word));
fo->rewrite(&file_image[off2], 2*sizeof(unsigned));
}
}
else if (xct_off < ioff) { // Slide subsequent PT_LOAD.
@ -590,7 +590,8 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft)
if ((user_init_off - ioff) < len) {
fo->seek(user_init_off + so_slide, SEEK_SET);
set_te32(&word, word);
unsigned word = cpr_entry;
set_te32(&word, cpr_entry);
fo->rewrite(&word, sizeof(word));
fo->seek(0, SEEK_END);
}
@ -612,7 +613,7 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft)
unsigned va = elf_unsigned_dynamic(Elf32_Dyn::DT_PLTGOT)
- (is_asl ? asl_delta : 0);
// Now use the old Phdrs (phdri)
Elf32_Phdr *phva;
Elf32_Phdr const *phva;
phva = elf_find_Phdr_for_va(va, phdri, e_phnum);
unsigned old_off = (va - get_te32(&phva->p_vaddr))
+ get_te32(&phva->p_offset);
@ -628,7 +629,17 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft)
fo->rewrite(&file_image[old_off], n_jmp_slot * 4);
}
}
if (j && shdr->sh_addr == 0
&& get_te32(&shdr->sh_offset) < xct_off) {
// Try to be nice by sliding; but still fails if compressed.
// So don't do it unless appending plain text of shstrtab.
unsigned sh_off = get_te32(&shdr->sh_offset);
if (xct_off < sh_off) {
set_te32(&shdr->sh_offset, sh_off + so_slide);
}
}
}
// Maybe: append plain text of shstrtab strings?
fo->seek(total_out, SEEK_SET);
if (xct_off < e_shoff) {
set_te32(&((Elf32_Ehdr *)lowmem.getVoidPtr())->e_shoff, total_out);
@ -781,7 +792,7 @@ off_t PackLinuxElf64::pack3(OutputFile *fo, Filter &ft)
&& !strcmp(".rel.plt", get_te32(&shdr->sh_name) + shstrtab)) {
upx_uint64_t va = elf_unsigned_dynamic(Elf64_Dyn::DT_PLTGOT) - asl_delta;
// Now use the old Phdrs (phdri)
Elf64_Phdr *phva;
Elf64_Phdr const *phva;
phva = elf_find_Phdr_for_va(va, phdri, e_phnum);
upx_uint64_t old_off = (va - get_te64(&phva->p_vaddr))
+ get_te64(&phva->p_offset);
@ -993,10 +1004,10 @@ PackLinuxElf64::PackLinuxElf64help1(InputFile *f)
check_pt_load(phdr);
}
// elf_find_dynamic() returns 0 if 0==dynseg.
dynstr = (char const *)elf_find_dynamic(Elf64_Dyn::DT_STRTAB);
dynsym = (Elf64_Sym const *)elf_find_dynamic(Elf64_Dyn::DT_SYMTAB);
gashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_GNU_HASH);
hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH);
dynstr = (char const *)elf_find_dynamic(Elf64_Dyn::DT_STRTAB);
dynsym = (Elf64_Sym /*const*/ *)elf_find_dynamic(Elf64_Dyn::DT_SYMTAB);
gashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_GNU_HASH);
hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH);
if (3& ((upx_uintptr_t)dynsym | (upx_uintptr_t)gashtab | (upx_uintptr_t)hashtab)) {
throwCantPack("unaligned DT_SYMTAB, DT_GNU_HASH, or DT_HASH/n");
}
@ -1921,7 +1932,7 @@ PackLinuxElf32::sort_DT32_offsets(Elf32_Dyn const *const dynp0)
if (!rva) {
continue; // not present in input
}
Elf32_Phdr *phdr = elf_find_Phdr_for_va(rva, phdri, e_phnum);
Elf32_Phdr const *phdr = elf_find_Phdr_for_va(rva, phdri, e_phnum);
if (!phdr) {
char msg[60]; snprintf(msg, sizeof(msg), "bad DT_{%#x} = %#x (no Phdr)",
k, rva);
@ -2601,7 +2612,7 @@ bool PackLinuxElf32::canPack()
}
// elf_find_dynamic() returns 0 if 0==dynseg.
dynstr= (char const *)elf_find_dynamic(Elf32_Dyn::DT_STRTAB);
dynsym= (Elf32_Sym const *)elf_find_dynamic(Elf32_Dyn::DT_SYMTAB);
dynsym= (Elf32_Sym /*const*/ *)elf_find_dynamic(Elf32_Dyn::DT_SYMTAB);
if (opt->o_unix.force_pie
|| Elf32_Dyn::DF_1_PIE & elf_unsigned_dynamic(Elf32_Dyn::DT_FLAGS_1)
@ -2703,6 +2714,8 @@ bool PackLinuxElf32::canPack()
user_init_rp = rp;
unsigned r_info = get_te32(&rp->r_info);
unsigned r_type = ELF32_R_TYPE(r_info);
set_te32(&dynsym[0].st_name, r_va); // for decompressor
set_te32(&dynsym[0].st_value, r_info);
if (Elf32_Ehdr::EM_ARM == e_machine) {
if (R_ARM_RELATIVE == r_type) {
user_init_va = get_te32(&file_image[user_init_off]);
@ -3024,7 +3037,7 @@ PackLinuxElf64::canPack()
}
// elf_find_dynamic() returns 0 if 0==dynseg.
dynstr= (char const *)elf_find_dynamic(Elf64_Dyn::DT_STRTAB);
dynsym= (Elf64_Sym const *)elf_find_dynamic(Elf64_Dyn::DT_SYMTAB);
dynsym= (Elf64_Sym /*const*/ *)elf_find_dynamic(Elf64_Dyn::DT_SYMTAB);
if (opt->o_unix.force_pie
|| Elf64_Dyn::DF_1_PIE & elf_unsigned_dynamic(Elf64_Dyn::DT_FLAGS_1)
@ -6323,7 +6336,10 @@ void PackLinuxElf32::un_DT_INIT(
case Elf32_Dyn::DT_RELA: { dt_rel = val; } break;
case Elf32_Dyn::DT_JMPREL: { dt_jmprel = val; } break;
case Elf32_Dyn::DT_PLTRELSZ: { dt_pltrelsz = val;
n_plt = 3+ (dt_pltrelsz / sizeof(Elf32_Rel)); // FIXME: "3+"
n_plt = dt_pltrelsz / sizeof(Elf32_Rel);
if (is_asl) {
n_plt += 3; // FIXME
}
}; break;
case Elf32_Dyn::DT_PLTGOT: { plt_va = dt_pltgot = val;}
@ -6331,43 +6347,73 @@ void PackLinuxElf32::un_DT_INIT(
case Elf32_Dyn::DT_PREINIT_ARRAY:
case Elf32_Dyn::DT_INIT_ARRAY:
case Elf32_Dyn::DT_FINI_ARRAY:
case Elf32_Dyn::DT_FINI: {
case Elf32_Dyn::DT_FINI: if (is_asl) {
set_te32(&dyn->d_val, val - asl_delta);
}; break;
} // end switch() on tag when is_asl
if (upx_dt_init == tag) {
if (Elf32_Dyn::DT_INIT == tag) {
if (Elf32_Dyn::DT_INIT == tag) { // the easy case
set_te32(&dyn->d_val, old_dtinit);
if (!old_dtinit) { // compressor took the slot
dyn->d_tag = Elf32_Dyn::DT_NULL;
dyn->d_val = 0;
}
}
// Apparently the hard case is common for some Android IDEs.
else if (Elf32_Dyn::DT_INIT_ARRAY == tag
|| Elf32_Dyn::DT_PREINIT_ARRAY == tag) {
// The slot must have a R_*_RELATIVE relocation (is_shlib,
// after all), but ElfXX_Rel ignores the initial contents!
// So changing the value will get ignored. Do it anyway.
// FIXME: we must fix the Rel ?
Elf32_Phdr const *phdr = phdro;
for (unsigned j = 0; j < e_phnum; ++j, ++phdr) {
upx_uint32_t vaddr = get_te32(&phdr->p_vaddr);
upx_uint32_t filesz = get_te32(&phdr->p_filesz);
unsigned q = val - (is_asl ? asl_delta : 0) - vaddr;
if (q < filesz) {
upx_uint32_t offset = get_te32(&phdr->p_offset);
// Rel overwrites the target; assumed default is 0
if (old_dtinit) {
upx_uint32_t oldval = 0;
set_te32(&oldval, old_dtinit);
// Counter-act unRel32 if asl_delta
// FIXME? the in-memory copy?
if (fo) {
fo->seek(q + offset, SEEK_SET);
fo->write(&oldval, sizeof(oldval));
}
// 'val' is the RVA of the first slot, which is the slot that
// the compressor changed to be the entry to the run-time stub.
Elf32_Rel *rp = (Elf32_Rel *)elf_find_dynamic(Elf32_Dyn::DT_NULL);
((Elf32_Dyn *)elf_find_dynptr(Elf32_Dyn::DT_NULL))->d_val = 0;
if (rp) {
// Compressor saved the original *rp in dynsym[0]
Elf32_Rel *rp_unc = (Elf32_Rel *)&dynsym[0]; // pointer
rp->r_info = rp_unc->r_info; // restore original r_info; r_offset not touched
unsigned e_entry = get_te32(&ehdri.e_entry);
unsigned init_rva = get_te32(&file_image[e_entry - 3*sizeof(unsigned)]);
unsigned arr_rva = get_te32(&rp_unc->r_offset);
Elf32_Phdr const *phdr = elf_find_Phdr_for_va(arr_rva, phdro, e_phnum);
unsigned arr_off = (arr_rva - get_te32(&phdr->p_vaddr)) + get_te32(&phdr->p_offset);
rp_unc->r_offset = 0; rp_unc->r_info = 0;
if (fo) {
fo->seek(elf_unsigned_dynamic(Elf32_Dyn::DT_SYMTAB), SEEK_SET);
fo->rewrite(rp_unc, sizeof(Elf32_Rel)); // clear dynsym[0]
fo->seek((char *)rp - (char *)&file_image[0], SEEK_SET);
fo->rewrite(rp, sizeof(*rp)); // restore original *rp
}
// Set arr[0] to the first user init routine.
unsigned r_info = get_te32(&rp->r_info);
unsigned r_type = ELF32_R_TYPE(r_info);
unsigned word;
if (Elf32_Ehdr::EM_ARM == e_machine) {
if (R_ARM_RELATIVE == r_type) {
set_te32(&word, init_rva);
}
break;
else if (R_ARM_ABS32 == r_type) {
word = 0;
}
}
else if (Elf32_Ehdr::EM_386 == e_machine) {
if (R_386_RELATIVE == r_type) {
}
else if (R_386_32 == r_type) {
}
if (R_386_RELATIVE == r_type) {
set_te32(&word, init_rva);
}
else if (R_386_32 == r_type) {
word = 0;
}
}
if (fo) {
fo->seek(arr_off, SEEK_SET);
fo->rewrite(&word, sizeof(unsigned));
fo->seek(0, SEEK_END);
}
}
}

View File

@ -197,7 +197,7 @@ protected:
virtual Elf32_Sym const *elf_lookup(char const *) const;
virtual unsigned elf_get_offset_from_address(unsigned) const;
virtual unsigned elf_get_offset_from_Phdrs(unsigned, Elf32_Phdr const *phdr0) const;
virtual Elf32_Phdr *elf_find_Phdr_for_va(unsigned addr, Elf32_Phdr *phdr, unsigned phnum);
virtual Elf32_Phdr const *elf_find_Phdr_for_va(unsigned addr, Elf32_Phdr const *phdr, unsigned phnum);
Elf32_Phdr const *elf_find_ptype(unsigned type, Elf32_Phdr const *phdr0, unsigned phnum);
Elf32_Shdr const *elf_find_section_name(char const *) const;
Elf32_Shdr *elf_find_section_type(unsigned) const;
@ -233,7 +233,7 @@ protected:
Elf32_Dyn *dynseg; // from PT_DYNAMIC
unsigned int const *hashtab, *hashend; // from DT_HASH
unsigned int const *gashtab, *gashend; // from DT_GNU_HASH
Elf32_Sym const *dynsym; // from DT_SYMTAB
Elf32_Sym *dynsym; // DT_SYMTAB; 'const' except [0] for decompressor
Elf32_Sym const *jni_onload_sym;
Elf32_Shdr *sec_strndx;
@ -385,7 +385,7 @@ protected:
Elf64_Dyn *dynseg; // from PT_DYNAMIC
unsigned int const *hashtab, *hashend; // from DT_HASH
unsigned int const *gashtab, *gashend; // from DT_GNU_HASH
Elf64_Sym const *dynsym; // from DT_SYMTAB
Elf64_Sym *dynsym; // DT_SYMTAB; 'const' except [0] for decompressor
Elf64_Sym const *jni_onload_sym;
Elf64_Shdr *sec_strndx;